/*
 * SPDX-License-Identifier: BSD-3-Clause
 *
 */
#include "board.h"
#include "hpm_nes_port.h"
#include "hpm_display_common.h"
#include "hpm_lcdc_drv.h"
#include "hpm_soc.h"
#include "hpm_l1c_drv.h"
#include "hpm_pdma_drv.h"
#include "hpm_i2s_drv.h"
#include "hpm_dao_drv.h"
#include "hpm_dma_drv.h"
#include "hpm_dmamux_drv.h"
#include "tusb.h"

#ifndef RUNNING_CORE_INDEX
#define RUNNING_CORE_INDEX HPM_CORE0
#endif

#define LV_COLOR_DEPTH 32
#define LCD_CONTROLLER BOARD_LCD_BASE
#define LCD_LAYER_INDEX (0)
#define LCD_LAYER_DONE_MASK (LCD_LAYER_INDEX + 1)
#define LCD_IRQ_NUM  BOARD_LCD_IRQ

#ifndef HPM_LCD_IRQ_PRIORITY
#define HPM_LCD_IRQ_PRIORITY  1
#endif

#define PIXEL_FORMAT display_pixel_format_rgb565

typedef struct audio_info {
    uint32_t sample_rate;
    uint8_t channel_num;
    uint8_t audio_depth;
} audio_info_t;

/*---------------------------------------------------------------------*
 * MACRO TYPEDEF CONSTANT ENUM DECLARATION
 *---------------------------------------------------------------------*/

#define MAX_REPORT  4

static uint8_t const keycode2ascii[128][2] = {HID_KEYCODE_TO_ASCII };

/* Each HID instance can have multiple reports */
static struct {
    uint8_t report_count;
    tuh_hid_report_info_t report_info[MAX_REPORT];
} hid_info[CFG_TUH_HID];

static void init_lcd(void);

static lcdc_layer_config_t layer;

ATTR_PLACE_AT_NONCACHEABLE_WITH_ALIGNMENT(HPM_L1C_CACHELINE_SIZE) color_t disp_buf[BOARD_LCD_WIDTH * BOARD_LCD_HEIGHT];

ATTR_ALIGN(HPM_L1C_CACHELINE_SIZE) audio_elem_t wave_buf[735 * 3];

static const audio_info_t k_audio_info = {
    8000, 1, 32,
};

volatile bool vsync = false;

volatile uint32_t key_value;

volatile uint32_t long_press_cnt;

void hpm_key_init(void)
{
    board_init_usb_pins();
    tusb_init();
}

void hpm_key_scan(void)
{
    tuh_task();
}

uint32_t hpm_key_read(void)
{
    return key_value;
}

void hpm_audio_init(void)
{
    board_init_dao_clock();
    init_dao_pins();
    memset(wave_buf, 0, sizeof(wave_buf));

    i2s_config_t i2s_config;
    i2s_transfer_config_t transfer;
    dao_config_t dao_config;

    i2s_get_default_config(DAO_I2S, &i2s_config);
    i2s_init(DAO_I2S, &i2s_config);
    /*
     * config transfer for DAO
     */
    i2s_get_default_transfer_config_for_dao(&transfer);
    if (status_success != i2s_config_tx(DAO_I2S, clock_get_frequency(clock_i2s1), &transfer)) {
        printf("I2S config failed for DAO\n");
        while (1) { }
    }

    dao_get_default_config(HPM_DAO, &dao_config);
    if (k_audio_info.channel_num == 1) {
        dao_config.enable_mono_output = true;
    }
    dao_init(HPM_DAO, &dao_config);

    dao_start(HPM_DAO);

    dmamux_config(HPM_DMAMUX, 0, HPM_DMA_SRC_I2S1_TX, true);
    i2s_enable_tx_dma_request(DAO_I2S);
}

void hpm_audio_play(void)
{
    dma_channel_config_t ch_config = { 0 };
    dma_default_channel_config(HPM_HDMA, &ch_config);
    ch_config.src_addr = core_local_mem_to_sys_address(HPM_CORE0, (uint32_t) wave_buf);
    ch_config.dst_addr = (uint32_t) &DAO_I2S->TXD[0];
    ch_config.src_width = DMA_TRANSFER_WIDTH_WORD;
    ch_config.dst_width = DMA_TRANSFER_WIDTH_WORD;
    ch_config.src_addr_ctrl = DMA_ADDRESS_CONTROL_INCREMENT;
    ch_config.dst_addr_ctrl = DMA_ADDRESS_CONTROL_FIXED;
    ch_config.size_in_byte = sizeof(wave_buf);
    ch_config.dst_mode = DMA_HANDSHAKE_MODE_HANDSHAKE;
    ch_config.src_burst_size = DMA_NUM_TRANSFER_PER_BURST_1T;

    uint32_t aligned_size = (sizeof(wave_buf) + HPM_L1C_CACHELINE_SIZE - 1) / 64 * 64;
    if (l1c_dc_is_enabled()) {
        /* cache writeback for sent buff */
        l1c_dc_writeback((uint32_t) wave_buf, aligned_size);
    }

    if (status_success != dma_setup_channel(HPM_HDMA, 0, &ch_config)) {
    }
}

void hpm_display_init(void)
{
    board_init_lcd();
    init_lcd();
    lcdc_turn_on_display(LCD_CONTROLLER);
}

void hpm_display_flush(area_t *area, color_t *buffer)
{
    vsync = false;
    while (!vsync) {
        __asm("nop");
    }
    uint32_t copy_size = (area->width * area->height) * sizeof(color_t);
    if (l1c_dc_is_enabled()) {
        uint32_t aligned_start = HPM_L1C_CACHELINE_ALIGN_DOWN((uint32_t) buffer);
        uint32_t aligned_end = HPM_L1C_CACHELINE_ALIGN_UP((uint32_t) buffer + copy_size);
        uint32_t aligned_size = aligned_end - aligned_start;
        l1c_dc_writeback(aligned_start, aligned_size);
    }

    uint32_t status;
    hpm_stat_t stat = pdma_scale(HPM_PDMA,
                                 (uint32_t) core_local_mem_to_sys_address(RUNNING_CORE_INDEX, (uint32_t) disp_buf),
                                 BOARD_LCD_WIDTH,
                                 (uint32_t) core_local_mem_to_sys_address(RUNNING_CORE_INDEX, (uint32_t) buffer),
                                 area->width,
                                 0,
                                 0,
                                 area->width,
                                 area->height,
                                 BOARD_LCD_WIDTH,
                                 BOARD_LCD_HEIGHT,
                                 0xff,
                                 PIXEL_FORMAT,
                                 true,
                                 &status);

    if (stat != status_success) {
        printf("pdma scale failed: 0x%x\n", status);
    }
}

void isr_lcd(void)
{
    volatile uint32_t s = lcdc_get_dma_status(BOARD_LCD_BASE);
    lcdc_clear_dma_status(BOARD_LCD_BASE, s);
    if (s & (1 << LCDC_DMA_ST_DMA0_DONE_SHIFT)) {
        if (lcdc_layer_control_shadow_loaded(BOARD_LCD_BASE, 0)) {
            vsync = true;
        }
    }
}
SDK_DECLARE_EXT_ISR_M(LCD_IRQ_NUM, isr_lcd)

/* Initialize your display and the required peripherals. */
static void init_lcd(void)
{
    display_pixel_format_t pixel_format;
    lcdc_config_t config = { 0 };
    lcdc_get_default_config(LCD_CONTROLLER, &config);

    config.resolution_x = BOARD_LCD_WIDTH;
    config.resolution_y = BOARD_LCD_HEIGHT;

    pixel_format = display_pixel_format_rgb565;

    lcdc_init(LCD_CONTROLLER, &config);
    lcdc_get_default_layer_config(LCD_CONTROLLER, &layer, pixel_format, LCD_LAYER_INDEX);

    memset(disp_buf, 0, sizeof(disp_buf));

    layer.position_x = 0;
    layer.position_y = 0;
    layer.width = BOARD_LCD_WIDTH;
    layer.height = BOARD_LCD_HEIGHT;
    layer.buffer = (uint32_t) core_local_mem_to_sys_address(RUNNING_CORE_INDEX, (uint32_t) &disp_buf);
    layer.background.u = 0;

    if (status_success != lcdc_config_layer(LCD_CONTROLLER, LCD_LAYER_INDEX, &layer, true)) {
        printf("failed to configure layer\n");
        while (1) {
        }
    }

    lcdc_turn_on_display(LCD_CONTROLLER);
    lcdc_enable_interrupt(LCD_CONTROLLER, LCD_LAYER_DONE_MASK << 16);
    intc_m_enable_irq_with_priority(LCD_IRQ_NUM, HPM_LCD_IRQ_PRIORITY);
}



/*---------------------------------------------------------------------*
 * TinyUSB Callbacks
 *---------------------------------------------------------------------*/

/* Invoked when device with hid interface is mounted
 * Report descriptor is also available for use. tuh_hid_parse_report_descriptor()
 * can be used to parse common/simple enough descriptor.
 * Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE, it will be skipped
 * therefore report_desc = NULL, desc_len = 0
 */
void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_report, uint16_t desc_len)
{
    printf("HID device address = %d, instance = %d is mounted\r\n", dev_addr, instance);

    /* Interface protocol (hid_interface_protocol_enum_t) */
    const char *protocol_str[] = { "None", "Keyboard", "Mouse" };
    uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance);

    printf("HID Interface Protocol = %s\r\n", protocol_str[itf_protocol]);

    /* By default, host stack will use activate boot protocol on supported interface.
     * Therefore, for this simple example, we only need to parse generic report descriptor (with built-in parser)
     */
    if (itf_protocol == HID_ITF_PROTOCOL_NONE) {
        hid_info[instance].report_count =
            tuh_hid_parse_report_descriptor(hid_info[instance].report_info, MAX_REPORT, desc_report, desc_len);
        printf("HID has %u reports \r\n", hid_info[instance].report_count);
    }

    /* request to receive report
     * tuh_hid_report_received_cb() will be invoked when report is available
     */
    if (!tuh_hid_receive_report(dev_addr, instance)) {
        printf("Error: cannot request to receive report\r\n");
    }
}

/* Invoked when device with hid interface is un-mounted */
void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance)
{
    printf("HID device address = %d, instance = %d is unmounted\r\n", dev_addr, instance);
}

/*---------------------------------------------------------------------*
* Keyboard
*---------------------------------------------------------------------*/

/* look up new key in previous keys */
static inline bool find_key_in_report(hid_keyboard_report_t const *report, uint8_t keycode)
{
    for (uint8_t i = 0; i < 6; i++) {
        if (report->keycode[i] == keycode) { return true; }
    }

    return false;
}

static void process_kbd_report(hid_keyboard_report_t const *report)
{
    bool is_key_released = true;
    uint32_t temp = 0;
    for (uint8_t i = 0; i < 6; i++) {
        if (report->keycode[i]) {
            is_key_released = false;
            bool const is_shift = report->modifier & (KEYBOARD_MODIFIER_LEFTSHIFT | KEYBOARD_MODIFIER_RIGHTSHIFT);
            uint8_t ch = keycode2ascii[report->keycode[i]][is_shift ? 1 : 0];
            switch (ch) {
            case 's':               // START
                temp |= (1 << 3);
                break;
            case 'a':               // X
                temp |= (1 << 2);
                break;
            case 'z':               // B
                temp |= (1 << 1);
                break;
            case 'x':               // A
                temp |= (1 << 0);
                break;
            case 'j':               // LEFT
                temp |= (1 << 6);
                break;
            case 'i':               // UP
                temp |= (1 << 4);
                break;
            case 'l':               // RIGHT
                temp |= (1 << 7);
                break;
            case 'k':               // DOWN
                temp |= (1 << 5);
                break;
            }

        } else {
        }
        if (is_key_released) {
            key_value = 0;
        } else {
            key_value = temp;
        }
    }
}


/*---------------------------------------------------------------------*
 * Generic Report
 *---------------------------------------------------------------------*/

static void process_generic_report(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len)
{
    (void) dev_addr;

    uint8_t const rpt_count = hid_info[instance].report_count;
    tuh_hid_report_info_t *rpt_info_arr = hid_info[instance].report_info;
    tuh_hid_report_info_t *rpt_info = NULL;

    if (rpt_count == 1 && rpt_info_arr[0].report_id == 0) {
        /* Simple report without report ID as 1st byte */
        rpt_info = &rpt_info_arr[0];
    } else {
        /* Composite report, 1st byte is report ID, data starts from 2nd byte */
        uint8_t const rpt_id = report[0];

        /* Find report id in the arrray */
        for (uint8_t i = 0; i < rpt_count; i++) {
            if (rpt_id == rpt_info_arr[i].report_id) {
                rpt_info = &rpt_info_arr[i];
                break;
            }
        }

        report++;
        len--;
    }

    if (!rpt_info) {
        printf("Couldn't find the report info for this report !\r\n");
        return;
    }

    /* For complete list of Usage Page & Usage checkout src/class/hid/hid.h. For examples:
     * - Keyboard                     : Desktop, Keyboard
     * - Mouse                        : Desktop, Mouse
     * - Gamepad                      : Desktop, Gamepad
     * - Consumer Control (Media Key) : Consumer, Consumer Control
     * - System Control (Power key)   : Desktop, System Control
     * - Generic (vendor)             : 0xFFxx, xx
     */
    if (rpt_info->usage_page == HID_USAGE_PAGE_DESKTOP) {
        switch (rpt_info->usage) {
        case HID_USAGE_DESKTOP_KEYBOARD:
            TU_LOG1("HID receive keyboard report\r\n");
            /* Assume keyboard follow boot report layout */
            process_kbd_report((hid_keyboard_report_t const *) report);
            break;

        default:
            break;
        }
    }
}

/* Invoked when received report from device via interrupt endpoint */
void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len)
{
    uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance);

    switch (itf_protocol) {
    case HID_ITF_PROTOCOL_KEYBOARD:
        TU_LOG2("HID receive boot keyboard report\r\n");
        process_kbd_report((hid_keyboard_report_t const *) report);
        break;

    default:
        /* Generic report requires matching ReportID and contents with previous parsed report info */
        process_generic_report(dev_addr, instance, report, len);
        break;
    }

    /* continue to request to receive report */
    if (!tuh_hid_receive_report(dev_addr, instance)) {
        printf("Error: cannot request to receive report\r\n");
    }
}
